package com.lifeonwalden.fincalc.finance.service;

import com.lifeonwalden.fincalc.bean.DateValuePair;
import com.lifeonwalden.fincalc.finance.bean.DrawdownInfo;
import com.lifeonwalden.fincalc.utils.date.DaysUtils;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

public interface RiskCalculator {
    /**
     * 计算回撤相关指标
     *
     * @param dataList  日期升序的数据
     * @param convertor
     * @param <T>
     * @return
     */
    static <T> DrawdownInfo calcDrawdown(List<T> dataList, Function<T, DateValuePair> convertor) {
        DrawdownInfo drawdownInfo = new DrawdownInfo();
        drawdownInfo.setMdd(BigDecimal.ZERO).setStartPoint(convertor.apply(dataList.get(0))).setEndPoint(drawdownInfo.getStartPoint())
                .setLongestWithoutNewHigherDays(0);

        DateValuePair maxPoint = drawdownInfo.getStartPoint();
        DateValuePair minPoint = drawdownInfo.getEndPoint();
        DateValuePair fixPoint = null;
        for (int i = 0; i < dataList.size(); i++) {
            DateValuePair point = convertor.apply(dataList.get(i));
            if (maxPoint.getValue().compareTo(point.getValue()) <= 0) {
                long daysWithoutHigher = DaysUtils.calcWithoutHeadTail(maxPoint.getDate(), point.getDate());
                if (daysWithoutHigher > drawdownInfo.getLongestWithoutNewHigherDays()) {
                    drawdownInfo.setLongestWithoutNewHigherDays((int) daysWithoutHigher).setLwnhdStartPoint(maxPoint).setLwnhdEndPoint(convertor.apply(dataList.get(i - 1)));
                }

                BigDecimal dd = InnerRiskCalculator.calcDrawdown(maxPoint.getValue(), minPoint.getValue());
                if (dd.compareTo(drawdownInfo.getMdd()) <= 0) {
                    drawdownInfo.setStartPoint(maxPoint).setEndPoint(minPoint).setMdd(dd);

                    fixPoint = null;
                }

                maxPoint = point;
                minPoint = point;

                if (null == fixPoint) {
                    fixPoint = point;
                }
            }
            if (minPoint.getValue().compareTo(point.getValue()) >= 0) {
                minPoint = point;
            }
            drawdownInfo.getMddSeries().add(new DateValuePair().setDate(point.getDate()).setValue(InnerRiskCalculator.calcDrawdown(maxPoint.getValue(), point.getValue())));
        }

        BigDecimal dd = InnerRiskCalculator.calcDrawdown(maxPoint.getValue(), minPoint.getValue());
        if (dd.compareTo(drawdownInfo.getMdd()) <= 0) {
            drawdownInfo.setStartPoint(maxPoint).setEndPoint(minPoint).setMdd(dd);
            fixPoint = null;
        }

        DateValuePair lastPoint = convertor.apply(dataList.get(dataList.size() - 1));
        drawdownInfo
                .setMddDays((int) DaysUtils.calcWithoutHeadOrTail(drawdownInfo.getStartPoint().getDate(), drawdownInfo.getEndPoint().getDate()))
                .setDd(InnerRiskCalculator.calcDrawdown(maxPoint.getValue(), lastPoint.getValue())).setFixPoint(fixPoint)
                .setCurrentWithoutNewHigherDays((int) DaysUtils.calcWithoutHeadOrTail(maxPoint.getDate(), lastPoint.getDate()));

        return drawdownInfo;
    }

    /**
     * 计算回撤相关指标及最大回撤持续期数（交易日）
     *
     * @param dataList  日期升序的数据
     * @param convertor
     * @param <T>
     * @return
     */
    static <T> DrawdownInfo calcDrawdownWithMddPeriods(List<T> dataList, Function<T, DateValuePair> convertor, BiFunction<Date, Date, Integer> periodCount) {
        DrawdownInfo drawdownInfo = calcDrawdown(dataList, convertor);
        drawdownInfo.setMddPeriods(periodCount.apply(drawdownInfo.getStartPoint().getDate(), drawdownInfo.getEndPoint().getDate()));

        return drawdownInfo;
    }

    /**
     * 计算年化波动率
     *
     * @param dataList 增长数据
     * @param fetcher
     * @param <T>
     * @return
     */
    static <T> BigDecimal calcAnnualVolatility(List<T> dataList, Function<T, BigDecimal> fetcher) {
        DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics();
        for (T data : dataList) {
            descriptiveStatistics.addValue(fetcher.apply(data).doubleValue());
        }

        return BigDecimal.valueOf(descriptiveStatistics.getStandardDeviation()).multiply(BigDecimal.valueOf(Math.pow(365, 0.5))).setScale(6, RoundingMode.HALF_UP);
    }
}
